package com.blue.http.server;

import java.io.IOException;
import java.nio.charset.Charset;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.TaskExecutor;

import com.blue.core.dict.HttpMethod;
import com.blue.http.annotation.HttpParser;
import com.blue.http.annotation.HttpUrlMethod;
import com.blue.http.config.HttpConfig;
import com.blue.http.config.WebsocketConfig;
import com.blue.http.invoke.Invoker;
import com.blue.http.message.DefaultHttpRequest;
import com.blue.http.message.DefaultUploadFile;
import com.blue.http.message.HttpResponse;
import com.blue.http.message.UploadFile;
import com.blue.http.message.View;
import com.blue.http.util.HttpServerUtil;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;

/**
 * @author zhengjin
 * @since 1.0 2018年04月19日
 */
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject>
{
	private static Logger logger = LoggerFactory.getLogger(HttpServerHandler.class);

	private final TaskExecutor taskExecutor;
	private final HttpConfig httpConfig;
	private final Invoker invoker;
	private final HttpParser httpParser;
	private final long maxUploadSize;
	private final String websocketRoot;
	private final static HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);

	private HttpRequest request;
	private DefaultHttpRequest httpRequest;
	private StringBuilder contentBuilder = new StringBuilder();
	private Charset charset;
	private boolean isText = true;
	private HttpPostRequestDecoder decoder;

	public HttpServerHandler(TaskExecutor taskExecutor, HttpConfig httpConfig, WebsocketConfig websocketConfig)
	{
		this.taskExecutor = taskExecutor;
		this.httpConfig = httpConfig;
		this.invoker = httpConfig.getInvoker();
		this.httpParser = httpConfig.getParser();
		this.maxUploadSize = httpConfig.getMaxUploadSize();

		if (websocketConfig == null)
		{
			this.websocketRoot = WebsocketConfig.ROOT;
		}
		else
		{
			this.websocketRoot = websocketConfig.getRoot();
		}
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception
	{
		if (msg instanceof HttpRequest)
		{
			request = (HttpRequest) msg;
			this.handleRequest(ctx, request);
		}
		else if (msg instanceof HttpContent)
		{
			HttpContent content = (HttpContent) msg;
			this.handleContent(ctx, content);
		}

	}

	private void handleRequest(ChannelHandlerContext ctx, HttpRequest request)
	{
		String uri = request.uri();
		if (uri.startsWith(websocketRoot))
		{
			ctx.fireChannelRead(request);
			return;
		}

		if (HttpUtil.is100ContinueExpected(request))
		{
			HttpServerUtil.send100Continue(ctx);
			return;
		}

		if (!request.decoderResult().isSuccess())
		{
			HttpServerUtil.sendError(ctx, HttpResponseStatus.BAD_REQUEST);
			return;
		}

		HttpMethod httpMethod = HttpMethod.valueOf(request.method().name());
		if (httpMethod == HttpMethod.POST)
		{
			long contentLength = HttpUtil.getContentLength(request, 0L);
			if (maxUploadSize > 0 && maxUploadSize < contentLength)
			{
				HttpServerUtil.sendError(ctx, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE);
				return;
			}
		}

		isText = HttpServerUtil.isPostText(request);
		String ip = HttpServerUtil.getIp(request.headers(), ctx.channel());
		httpRequest = new DefaultHttpRequest(httpMethod, ip);
		httpRequest.setContentLength(HttpUtil.getContentLength(request, 0L));
		httpRequest.parseUri(uri, httpConfig.getPath());
		httpRequest.parseHeaders(request.headers());
		HttpUrlMethod urlMethod = httpParser.getUrlMethod(httpRequest.getUrl(), httpMethod);
		if (urlMethod == null)
		{
			HttpStaticHandler.handle(ctx, request, httpConfig, httpRequest.getUrl());
			return;
		}
		charset = Charset.forName(urlMethod.getCharset().getName());
		decoder = new HttpPostRequestDecoder(factory, request);
	}

	private void handleContent(ChannelHandlerContext ctx, HttpContent content)
	{
		if (httpRequest == null)
		{
			ctx.fireChannelRead(content.retain());
			return;
		}

		if (isText && httpRequest.getHttpMethod() == HttpMethod.POST)
		{
			String c = charset == null ? content.content().toString() : content.content().toString(charset);
			contentBuilder.append(c);
		}

		if (decoder == null)
			return;

		decoder.offer(content);
		try
		{
			while (decoder.hasNext())
			{
				InterfaceHttpData data = decoder.next();
				if (data != null)
				{
					String name = data.getName();
					if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload)
					{
						FileUpload fileUpload = (FileUpload)data;
						if (fileUpload.isCompleted())
						{
							UploadFile uploadFile = new DefaultUploadFile(fileUpload);
							httpRequest.getFileMap().put(name, uploadFile);
							logger.info("上传文件：成功 - {}", data);
						}
					}
					else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute
							|| data.getHttpDataType() == InterfaceHttpData.HttpDataType.InternalAttribute)
					{
						Attribute attribute = (Attribute)data;
						String value = attribute.getString();
						httpRequest.getPostMap().put(name, value);
					}
				}
			}
		}
		catch (HttpPostRequestDecoder.EndOfDataDecoderException e)
		{
			//logger.warn("到达流末尾：");
		}
		catch (IOException e)
		{
			logger.warn("上传文件出错：", e);
		}

		if (content instanceof LastHttpContent)
		{
			httpRequest.setContent(contentBuilder.toString());
			taskExecutor.execute(() ->
			{
				HttpResponse response = invoker.invoke(httpRequest);
				decoder.destroy();
				decoder = null;
				this.handleResponse(ctx, request, response);
			});
		}
	}


	private void handleResponse(ChannelHandlerContext ctx, HttpRequest request, HttpResponse response)
	{
		if (response.getDownload() != null) // 下载文件
		{
			HttpStaticHandler.download(ctx, request, response.getDownload());
		}
		else if (response.isView())
		{
			View view = response.getView();
			if (view.location() != null && !view.location().isEmpty()) // 跳转页面
			{
				HttpServerUtil.sendRedirect(ctx, view.location());
			}
			else // 渲染页面
			{
				String html = httpConfig.render(view.view(), response.param());
				if (html == null || html.isEmpty())
				{
					HttpServerUtil.sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
				}
				else
				{
					response.setResult(html);
					HttpServerUtil.sendHtml(ctx, request, response);
				}
			}
		}
		else // API接口
		{
			HttpServerUtil.sendHtml(ctx, request, response);
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception
	{
		super.exceptionCaught(ctx, cause);
		Channel ch = ctx.channel();
		ChannelId id = ch.id();
		logger.error("Http 客户端发生错误，断开连接：{}" + ch.remoteAddress() + "，id=" + id, cause);
		if (ctx.channel().isActive())
		{
			HttpServerUtil.sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
		}
	}

}
